﻿using Lhz.Serialization;
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;

namespace Lhz.InternetService
{
    public class ServiceInvoker : IServiceInvoker
    {
        private Dictionary<string, ServiceProxy> serviceProxies = new Dictionary<string, ServiceProxy>();
        private Type[] referencedTypes;
        private List<Assembly> referencedAssemblies = new List<Assembly>();

        public bool CacheProxy { get; set; }

        public bool AllowCookie { get; set; }

        public ServiceInvoker(params Type[] referencedTypes)
        {
            this.referencedTypes = referencedTypes;
            CacheProxy = true;
        }

        public void CacheProxyClient(string url, object[] arguments)
        {
            if (CacheProxy)
            {
                GetServiceProxyEntity(url);
            }
            else
            {
                throw new ApplicationException("缓存未开启。");
            }
        }

        protected virtual ServiceProxy BuildServiceProxy(string url)
        {
            var uri = new Uri(url + "?wsdl");
            var meClient = new MetadataExchangeClient(uri, MetadataExchangeClientMode.HttpGet);
            var ms = meClient.GetMetadata();
            var wi = new WsdlImporter(ms);
            if (!url.EndsWith("asmx"))
            {
                var io = new ImportOptions();
                var xdci = new XsdDataContractImporter() { Options = io };
                io.ImportXmlType = true;
                io.GenerateSerializable = true;
                if (referencedTypes != null)
                {
                    foreach (var type in referencedTypes)
                    {
                        io.ReferencedTypes.Add(type);
                        if (!type.Assembly.GlobalAssemblyCache && !referencedAssemblies.Contains(type.Assembly))
                        {
                            referencedAssemblies.Add(type.Assembly);
                        }
                    }
                }
                wi.State.Add(typeof(XsdDataContractImporter), xdci);
            }

            if (wi.Errors.Count > 0)
            {
                Debug.WriteLine("Wsdl错误：");
                foreach (var error in wi.Errors)
                {
                    Debug.WriteLine(error.Message);
                }
            }

            var sec = wi.ImportAllEndpoints();
            Debug.WriteLine("终结点：");
            foreach (var item in sec)
            {
                Debug.WriteLine(item.Binding.Scheme);
            }
            var se = sec.First();

            var scg = new ServiceContractGenerator();
            foreach (var cd in wi.ImportAllContracts())
            {
                scg.GenerateServiceContractType(cd);
            }
            foreach (var referencedAssembly in referencedAssemblies)
            {
                for (int i = 0; i < scg.TargetCompileUnit.ReferencedAssemblies.Count; i++)
                {
                    var referencedAssemblyName = scg.TargetCompileUnit.ReferencedAssemblies[i];
                    if (Path.GetFileName(referencedAssembly.Location) == referencedAssemblyName)
                    {
                        scg.TargetCompileUnit.ReferencedAssemblies[i] = referencedAssembly.Location;
                    }
                }
            }

            var cscp = new CSharpCodeProvider();
            var cgo = new CodeGeneratorOptions();
            var writer = new StringWriter();
            cscp.GenerateCodeFromCompileUnit(scg.TargetCompileUnit, writer, cgo);
            Debug.WriteLine("生成代码：");
            Debug.WriteLine(writer.ToString());

            var cp = new CompilerParameters();
            cp.GenerateExecutable = false;
            cp.GenerateInMemory = true;
            var cr = cscp.CompileAssemblyFromDom(cp, scg.TargetCompileUnit);
            if (cr.Errors.HasErrors)
            {
                var sb = new StringBuilder();
                foreach (CompilerError ce in cr.Errors)
                {
                    sb.AppendLine(ce.ToString());
                }
                throw new ApplicationException("编译错误：" + sb.ToString());
            }

            var proxyTypes = cr.CompiledAssembly.ExportedTypes.Where(r => r.BaseType != null && r.BaseType.IsGenericType && r.BaseType.GetGenericTypeDefinition() == typeof(ClientBase<>)).ToList();
            if (proxyTypes.Count != 1)
            {
                throw new ApplicationException("代理类不唯一。");
            }
            var proxyType = proxyTypes.First();
            proxyType.BaseType.GetProperty("CacheSetting").SetValue(null, CacheSetting.AlwaysOn);

            var sp = new ServiceProxy();
            sp.Type = proxyTypes.First();
            sp.EndpointAddress = se.Address;
            sp.Binding = se.Binding;
            var js = new JsonSerializer();
            sp.AllowCookieBinding = (Binding)js.Deserialize(sp.Binding.GetType(), js.Serialize(sp.Binding));
            if (sp.AllowCookieBinding is BasicHttpBinding)
            {
                var binding = sp.AllowCookieBinding as BasicHttpBinding;
                binding.AllowCookies = true;
            }
            return sp;
        }

        private object BuildProxy(ServiceProxy sp)
        {
            var proxy = Activator.CreateInstance(sp.Type, AllowCookie ? sp.AllowCookieBinding : sp.Binding, sp.EndpointAddress);
            return proxy;
        }

        private ServiceProxy GetServiceProxyEntity(string url)
        {
            if (serviceProxies.ContainsKey(url))
            {
                return serviceProxies[url];
            }
            else
            {
                var proxyEntity = BuildServiceProxy(url);
                if (CacheProxy)
                {
                    serviceProxies.Add(url, proxyEntity);
                }
                return proxyEntity;
            }
        }

        public object Invoke(string url, string methodName, params object[] arguments)
        {
            var sp = GetServiceProxyEntity(url);
            return Invoke(sp, methodName, arguments);
        }

        public T Invoke<T>(string url, string methodName, params object[] arguments)
        {
            var sp = GetServiceProxyEntity(url);
            return (T)Invoke(sp, methodName, arguments);
        }

        public Type[] GetMethodParameterTypes(string url, string methodName)
        {
            var sp = GetServiceProxyEntity(url);
            var proxy = BuildProxy(sp);
            var methodInfo = GetMethodInfo(sp, methodName);
            return methodInfo.GetParameters().Select(r => r.ParameterType).ToArray();
        }

        private MethodInfo GetMethodInfo(ServiceProxy sp, string methodName)
        {
            var methodInfo = sp.Type.GetMethod(methodName);
            if (methodInfo == null)
            {
                throw new ApplicationException("[" + sp.EndpointAddress.Uri.ToString() + "]服务未包含[" + methodName + "]方法。");
            }
            else
            {
                return methodInfo;
            }
        }

        private object Invoke(ServiceProxy sp, string methodName, object[] arguments, Action<Type[], object[]> actionArgumentConverter = null)
        {
            var proxy = BuildProxy(sp);
            try
            {
                var methodInfo = GetMethodInfo(sp, methodName);
                var parameterInfos = methodInfo.GetParameters();
                var argumentCount = 0;
                if (arguments != null)
                {
                    argumentCount = arguments.Length;
                }
                if (argumentCount != parameterInfos.Length)
                {
                    throw new ArgumentException("提供的参数与方法" + methodName + "参数的个数不一致。", "arguments");
                }
                else
                {
                    if (actionArgumentConverter != null)
                    {
                        actionArgumentConverter(parameterInfos.Select(r => r.ParameterType).ToArray(), arguments);
                    }
                    var js = new JsonSerializer();
                    if (parameterInfos.Length > 0)
                    {
                        for (int i = 0; i < parameterInfos.Length; i++)
                        {
                            var parameterType = parameterInfos[i].ParameterType;
                            var argument = arguments[i];
                            if (argument != null)
                            {
                                if (parameterType.IsByRef)
                                {
                                    parameterType = parameterType.GetElementType();
                                }
                                if (!argument.GetType().IsAssignableFrom(parameterType))
                                {
                                    Debug.WriteLine("类型序列化转换：" + argument.GetType() + "," + parameterType);
                                    arguments[i] = js.Deserialize(parameterType, js.Serialize(argument));
                                }
                            }
                        }
                    }
                    object result = null;
                    result = methodInfo.Invoke(proxy, arguments);
                    proxy.GetType().GetMethod("Close").Invoke(proxy, null);
                    return result;
                }
            }
            finally
            {
                proxy.GetType().GetMethod("Abort").Invoke(proxy, null);
            }
        }

        protected class ServiceProxy
        {
            public EndpointAddress EndpointAddress { get; set; }

            public Binding Binding { get; set; }

            public Binding AllowCookieBinding { get; set; }

            public Type Type { get; set; }
        }
    }
}
